/**
 * 阿里云OSS文件上传工具
 * 不使用官方SDK，基于HTTP请求实现
 * 函数式风格实现
 */
import crypto from 'crypto';
import https from 'https';
import http from 'http';
import { URL } from 'url';
import fs from 'fs';
import path from 'path';
import log4js from './log4js.js';

const logger = log4js.getLogger();

/**
 * 创建阿里云OSS客户端
 * @param {Object} options 配置选项
 * @param {String} options.accessKeyId 阿里云AccessKeyId
 * @param {String} options.accessKeySecret 阿里云AccessKeySecret
 * @param {String} options.bucket OSS存储空间名称
 * @param {String} options.region 地域，例如：oss-cn-hangzhou
 * @param {String} options.endpoint 自定义域名（可选）
 * @returns {Object} OSS客户端对象
 */
const createOssClient = (options) => {
    // 验证必要参数
    if (!options.accessKeyId || !options.accessKeySecret || !options.bucket || !options.region) {
        throw new Error('缺少必要的OSS配置参数');
    }

    // 内部状态
    const state = {
        accessKeyId: options.accessKeyId,
        accessKeySecret: options.accessKeySecret,
        bucket: options.bucket,
        region: options.region,
        endpoint: options.endpoint || `${options.bucket}.${options.region}.aliyuncs.com`,
        secure: options.secure !== false // 默认使用HTTPS
    };

    /**
     * 生成OSS签名
     * @param {String} method HTTP方法
     * @param {String} resource 资源路径
     * @param {Object} headers HTTP头信息
     * @param {Object} queries URL查询参数
     * @returns {String} 签名字符串
     */
    const generateSignature = (method, resource, headers = {}, queries = {}) => {
        // 1. 组织CanonicalizedOSSHeaders
        const ossHeaders = {};
        Object.keys(headers).forEach(key => {
            const lowerKey = key.toLowerCase();
            if (lowerKey.startsWith('x-oss-')) {
                ossHeaders[lowerKey] = headers[key];
            }
        });

        const canonicalizedOSSHeadersArray = Object.keys(ossHeaders).sort().map(key => {
            return `${key}:${ossHeaders[key]}\n`;
        });
        const canonicalizedOSSHeaders = canonicalizedOSSHeadersArray.join('');

        // 2. 组织CanonicalizedResource
        let canonicalizedResource = `/${state.bucket}${resource}`;
        if (Object.keys(queries).length > 0) {
            const queryKeys = Object.keys(queries).sort();
            const queryPairs = queryKeys.map(key => {
                return queries[key] === '' ? key : `${key}=${queries[key]}`;
            });
            canonicalizedResource += '?' + queryPairs.join('&');
        }

        // 3. 组织签名字符串
        const date = headers['Date'] || new Date().toUTCString();
        const contentType = headers['Content-Type'] || '';
        const contentMd5 = headers['Content-MD5'] || '';

        const stringToSign = [
            method,
            contentMd5,
            contentType,
            date,
            canonicalizedOSSHeaders + canonicalizedResource
        ].join('\n');

        // 4. 计算签名
        const signature = crypto
            .createHmac('sha1', state.accessKeySecret)
            .update(stringToSign)
            .digest('base64');

        return signature;
    };

    /**
     * 发送HTTP请求
     * @param {Object} options 请求选项
     * @returns {Promise} 请求结果Promise
     */
    const request = (options) => {
        return new Promise((resolve, reject) => {
            const method = options.method || 'GET';
            const resource = options.resource || '/';
            const queries = options.queries || {};
            const headers = options.headers || {};
            const body = options.body || null;

            // 设置公共头信息
            headers['Date'] = headers['Date'] || new Date().toUTCString();
            headers['Host'] = state.endpoint;
            headers['x-oss-date'] = headers['Date'];

            // 计算签名
            const signature = generateSignature(method, resource, headers, queries);
            headers['Authorization'] = `OSS ${state.accessKeyId}:${signature}`;

            // 构建URL
            let urlStr = `${state.secure ? 'https' : 'http'}://${state.endpoint}${resource}`;
            if (Object.keys(queries).length > 0) {
                const url = new URL(urlStr);
                Object.keys(queries).forEach(key => {
                    url.searchParams.append(key, queries[key]);
                });
                urlStr = url.toString();
            }

            const url = new URL(urlStr);
            const reqOptions = {
                method,
                hostname: url.hostname,
                path: url.pathname + url.search,
                headers
            };

            // 选择HTTP或HTTPS模块
            const httpModule = state.secure ? https : http;

            const req = httpModule.request(reqOptions, (res) => {
                let data = '';
                res.on('data', (chunk) => {
                    data += chunk;
                });

                res.on('end', () => {
                    if (res.statusCode >= 200 && res.statusCode < 300) {
                        resolve({
                            statusCode: res.statusCode,
                            headers: res.headers,
                            body: data,
                            url: urlStr
                        });
                    } else {
                        reject({
                            statusCode: res.statusCode,
                            headers: res.headers,
                            body: data,
                            url: urlStr
                        });
                    }
                });
            });

            req.on('error', (err) => {
                logger.error(`OSS请求错误: ${err.message}`);
                reject(err);
            });

            if (body) {
                req.write(body);
            }

            req.end();
        });
    };

    /**
     * 上传文件
     * @param {String} objectKey 对象键（OSS中的文件路径）
     * @param {String|Buffer} fileContent 文件内容（字符串或Buffer）
     * @param {Object} options 上传选项
     * @returns {Promise} 上传结果Promise
     */
    const uploadFile = async (objectKey, fileContent, options = {}) => {
        try {
            // 处理文件内容
            let content = fileContent;
            let contentType = options.contentType || 'application/octet-stream';

            // 如果是文件路径，则读取文件内容
            if (typeof fileContent === 'string' && fs.existsSync(fileContent)) {
                content = fs.readFileSync(fileContent);
                // 根据文件扩展名推断内容类型
                const ext = path.extname(fileContent).toLowerCase();
                if (ext) {
                    const mimeTypes = {
                        '.jpg': 'image/jpeg',
                        '.jpeg': 'image/jpeg',
                        '.png': 'image/png',
                        '.gif': 'image/gif',
                        '.pdf': 'application/pdf',
                        '.doc': 'application/msword',
                        '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                        '.xls': 'application/vnd.ms-excel',
                        '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                        '.txt': 'text/plain',
                        '.html': 'text/html',
                        '.htm': 'text/html',
                        '.json': 'application/json',
                        '.xml': 'application/xml',
                        '.mp3': 'audio/mpeg',
                        '.mp4': 'video/mp4',
                        '.zip': 'application/zip'
                    };
                    contentType = mimeTypes[ext] || contentType;
                }
            }

            // 计算内容MD5
            const contentMd5 = crypto
                .createHash('md5')
                .update(Buffer.isBuffer(content) ? content : Buffer.from(content))
                .digest('base64');

            // 准备请求头
            const headers = {
                'Content-Type': contentType,
                'Content-MD5': contentMd5,
                'Content-Length': Buffer.isBuffer(content) ? content.length : Buffer.from(content).length,
                ...options.headers
            };

            // 发送上传请求
            const result = await request({
                method: 'PUT',
                resource: `/${objectKey}`,
                headers,
                body: content
            });

            return {
                success: true,
                url: `https://${state.endpoint}/${objectKey}`,
                etag: result.headers.etag,
                ...result
            };
        } catch (error) {
            logger.error(`文件上传失败: ${error.message}`);
            return {
                success: false,
                error: error.message,
                details: error
            };
        }
    };

    /**
     * 上传本地文件
     * @param {String} localFilePath 本地文件路径
     * @param {String} objectKey 对象键（OSS中的文件路径）
     * @param {Object} options 上传选项
     * @returns {Promise} 上传结果Promise
     */
    const uploadLocalFile = (localFilePath, objectKey, options = {}) => {
        return uploadFile(objectKey, localFilePath, options);
    };

    /**
     * 获取文件访问URL
     * @param {String} objectKey 对象键（OSS中的文件路径）
     * @param {Number} expires URL过期时间（秒），默认3600秒
     * @returns {String} 访问URL
     */
    const getSignedUrl = (objectKey, expires = 3600) => {
        const expiration = Math.floor(Date.now() / 1000) + expires;
        const resource = `/${objectKey}`;

        // 准备签名参数
        const queries = {
            'OSSAccessKeyId': state.accessKeyId,
            'Expires': expiration.toString(),
            'Signature': ''
        };

        // 生成签名字符串
        const stringToSign = [
            'GET',
            '',
            '',
            expiration.toString(),
            `/${state.bucket}${resource}`
        ].join('\n');

        // 计算签名
        queries.Signature = crypto
            .createHmac('sha1', state.accessKeySecret)
            .update(stringToSign)
            .digest('base64');

        // 构建URL
        const queryString = Object.keys(queries)
            .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(queries[key])}`)
            .join('&');

        return `${state.secure ? 'https' : 'http'}://${state.endpoint}${resource}?${queryString}`;
    };

    /**
     * 删除文件
     * @param {String} objectKey 对象键（OSS中的文件路径）
     * @returns {Promise} 删除结果Promise
     */
    const deleteFile = async (objectKey) => {
        try {
            const result = await request({
                method: 'DELETE',
                resource: `/${objectKey}`
            });

            return {
                success: true,
                ...result
            };
        } catch (error) {
            logger.error(`文件删除失败: ${error.message}`);
            return {
                success: false,
                error: error.message,
                details: error
            };
        }
    };

    /**
     * 检查文件是否存在
     * @param {String} objectKey 对象键（OSS中的文件路径）
     * @returns {Promise<Boolean>} 文件是否存在
     */
    const isFileExist = async (objectKey) => {
        try {
            await request({
                method: 'HEAD',
                resource: `/${objectKey}`
            });
            return true;
        } catch (error) {
            if (error.statusCode === 404) {
                return false;
            }
            throw error;
        }
    };

    // 返回客户端对象
    return {
        uploadFile,
        uploadLocalFile,
        getSignedUrl,
        deleteFile,
        isFileExist,
        request
    };
};

// 使用示例
/*
const ossClient = createOssClient({
  accessKeyId: '您的AccessKeyId',
  accessKeySecret: '您的AccessKeySecret',
  bucket: '您的Bucket名称',
  region: 'oss-cn-hangzhou'
});

// 上传字符串内容
async function uploadExample() {
  const result = await ossClient.uploadFile(
    'test/example.txt',
    'Hello, OSS!',
    { contentType: 'text/plain' }
  );
  console.log(result);
}

// 上传本地文件
async function uploadLocalFileExample() {
  const result = await ossClient.uploadLocalFile(
    '/path/to/local/file.jpg',
    'images/file.jpg'
  );
  console.log(result);
}

// 获取签名URL
const url = ossClient.getSignedUrl('images/file.jpg', 3600);
console.log('签名URL:', url);
*/

export default {
    createOssClient
};